/************************************************************************************
* Collector.cpp                                                                     *
*                                                                                   *
* Copyright (C) 2011 Varuna L Amachi                                                *
*                                                                                   *
* This program is free software; you can redistribute it and/or modify              *
* it under the terms of the GNU General Public License as published by              *
* the Free Software Foundation; either version 2 of the License, or any             *
* later version.                                                                    *
*                                                                                   *
* This program is distributed in the hope that it will be useful, but               *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY        *
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for       *
* more details. You should have received a copy of the GNU General Public           *
* License along with this program; if not, write to the Free Software Foundation,   *
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                       *
*                                                                                   *
*************************************************************************************/

#include "Collector.h"

#include "common/Utils.h"
#include "common/Constants.h"
#include "database/YDb.h"
#include "common/YLog.h"

#include <QBrush>
#include <QColor>


Collector *Collector::__instance = NULL;

void Collector::init(QWidget *parent) {
    if(__instance == NULL) {
        __instance = new Collector(parent);
    }
    else {
        __instance->setParent(parent);
    }
}


Collector *Collector::get() {
    if(__instance == NULL) {
        YLog::logError("Collector instance is requested"
                       " before it is not initialized!");
    }
    return __instance;
}

Collector::Collector(QObject *parent) :
    QAbstractItemModel(parent) {
//    QStandardItemModel(parent) {
    m_itemMap = YDb::get()->retriever()->retrieveCollection();
    m_items   = m_itemMap->values();
    m_valid = false;
    m_thread = new CollectionDeleterThread(m_itemMap, this);
    m_artistInfo.insert(QString(ALL_ARTISTS), new ArtistInfo(ALL_ARTISTS));
    populateAuxStrcts();
    connect(m_thread, SIGNAL(removalComplete()), this, SLOT(threadCompletedRemoval()));
    connect(this, SIGNAL(itemsAdded(QList<CollectorItem*>*)),
            this, SLOT(addToAuxStructs(QList<CollectorItem*>*)));
    connect(this, SIGNAL(aboutToRemoveItems(QList<QString>*)),
            this, SLOT(removeFromAuxStructs(QList<QString>*)));
}


int Collector::rowCount(const QModelIndex &) const{
    return m_itemMap->size();
}


int Collector::columnCount(const QModelIndex &) const {
    return 5; //for now
}


QVariant Collector::data(const QModelIndex &index, int role) const {
    if(! index.isValid() || index.row() >= m_itemMap->size()) {
        return QVariant();
    }
    if (! index.isValid()) {
        return QVariant();
    }
    if (role == Qt::TextAlignmentRole) {
        return int (Qt::AlignLeft | Qt::AlignVCenter);
    }
    else if (role == Qt::DisplayRole) {
        CollectorItem* source = m_items.at(index.row());
        switch(index.column()) {
        case 0: return source->trackId();
        case 1: return source->title();
        case 2: return source->artist();
        case 3: return source->album();
        case 4: return Utils::getIntegerFromRating(source->rating());
        }
    }
    else if(role == Qt::ToolTipRole) {
        CollectorItem* source = m_items.at(index.row());
        QString tooltip = QString() +
                tr("Title  : <b>")+source->title()+"</b><br>"+
                tr("Artist : <b>")+source->artist()+"</b><br>"+
                tr("Album  : <b>")+source->album()+"</b>";
        return tooltip;
    }
    else if(role == Qt::SizeHintRole) {
        return QSize(0, 18);
    }
    else if(role == Qt::FontRole) {
        return QFont("Droid Sans", 9);
    }
    return QVariant();
}



Qt::ItemFlags Collector::flags(const QModelIndex &index) const {
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    if(index.column() == 4) {
        return flags |= Qt::ItemIsEditable;
    }
    return flags;
}



QVariant Collector::headerData(int section, Qt::Orientation, int role) const {
    if (role == Qt::TextAlignmentRole) {
        return int (Qt::AlignLeft | Qt::AlignVCenter);
    }
    else if (role == Qt::DisplayRole) {
        switch(section) {
        case 0: return tr("UUID");
        case 1: return tr("Title");
        case 2: return tr("Artist");
        case 3: return tr("Album");
        case 4: return tr("Rating");
        }
    }
    return QVariant();
}


bool Collector::setData(const QModelIndex &index, const QVariant &value, int role) {
    if(index.column() == 4) {
        CollectorItem* item = m_items.at(index.row());
        item->setRating(Utils::getMediaRating(value.toInt()));
        YDb::get()->updater()->updateTrackRating(item, value.toInt());
        emit modelItemChanged(item);
        return true;
    }
    return false;
}



QModelIndex Collector::index(int row, int column, const QModelIndex &) const {
    return createIndex(row, column);
}


QModelIndex Collector::parent(const QModelIndex &index)  const {
    return QModelIndex();
}


bool Collector::hasChildren(const QModelIndex &parent) const {
    if(! parent.isValid()) {
        return true;
    }
    return false;
}




void Collector::add(QList<CollectorItem*>* items) {
    if( ! items->empty()) {
        beginInsertRows(QModelIndex(), m_items.size(),
                        (m_items.size() + items->size()) - 1);
        m_items.append((*items));
        foreach(CollectorItem* item, (*items)) {
            m_itemMap->insert(item->trackId(), item);
        }
//        moving to loader which runs in seperate thread
//        YDb::get()->updater()->addCollectionItems(*items);
//        YDb::get()->updater()->addRecentAdditions(*items);
        endInsertRows();
        emit itemsAdded(items);
    }
}


void Collector::mediaRatingChanged(CollectorItem *item, MediaRating rating) {
    item->setRating(rating);
    YDb::get()->updater()->updateTrackRating(item,
                        Utils::getIntegerFromRating(rating));
    if(rating != None) {
        AlbumInfo *album = m_artistInfo[ALL_ARTISTS]->m_albums->value(item->album());
        album->m_avgRating = (album->m_avgRating + int(rating)) / 2;
    }
}


void Collector::remove(QList<QString> *ids) {
    emit aboutToRemoveItems(ids);
    emit collectionDeletionStarted();
    m_thread->setData(ids);
    m_thread->start();
}


void Collector::threadCompletedRemoval() {
    beginResetModel();
    m_items.clear();
    m_items = m_itemMap->values();
    endResetModel();
    emit collectionDeletionEnded();
}

void Collector::clear() {
    if(! m_itemMap->empty()) {
        QList<QString> *toBeDeleted = new QList<QString>();
        for(QHash<QString, CollectorItem *>::iterator it = m_itemMap->begin();
                it != m_itemMap->end();
                ++it) {
            toBeDeleted->append(it.key());
        }
        this->remove(toBeDeleted);
//        beginRemoveRows(QModelIndex(), 0, m_items.size());
//        foreach(CollectorItem* item, m_items) {
//            delete item;
//        }
//        m_items.clear();
//        m_itemMap->clear();
//        endRemoveRows();
//        emit collectorCleared();
    }
}


void Collector::itemPlayed(MediaItem *item) {
    if(item->itemType() == "CollectorItem") {
        CollectorItem *colItem = dynamic_cast<CollectorItem *>(item);
        colItem->setPlayCount(colItem->playCount() + 1);
        AlbumInfo *album = m_artistInfo.value(ALL_ALBUMS)->m_albums->value(item->artist()+":"+colItem->album());
        album->m_numPlays += 1;
        YDb::get()->updater()->incrementPlayCount(colItem);
        YDb::get()->updater()->addHistory(colItem);
    }
}


void Collector::itemFinishedPlaying(MediaItem* item) {
    //Nothing to do as of now
}

Collector::~Collector() {
    for(QHash<QString, CollectorItem *>::iterator it = m_itemMap->begin();
        it != m_itemMap->end();
        ++ it) {
        delete it.value();
    }
    m_itemMap->clear();
    clearAuxStructs();
    delete m_itemMap;
}


quint32 Collector::numItems() {
    return m_itemMap->size();
}


CollectorItem* Collector::item(QString uuid) {
    if(m_itemMap->contains(uuid)) {
        return m_itemMap->value(uuid);
    }
    return NULL;
}


CollectorItem* Collector::item(int row) {
    if(m_valid && row >= 0 && row < numItems()) {
        return m_items.at(row);
    }
    return NULL;
}

int Collector::items(QList<QString> &trackIds,
                     QHash<QString, CollectorItem *> &outItems) {
    int collected;
    foreach(QString id, trackIds) {
        if(m_itemMap->contains(id)) {
            CollectorItem *item = m_itemMap->value(id);
            outItems.insert(item->trackId(), item);
            ++ collected;
        }
    }
    return collected;
}


int Collector::items(QList<QString> &trackIds, QList<CollectorItem *> &outItems) {
    int collected;
    for(int i = 0; i < trackIds.size(); ++i) {
        if(m_itemMap->contains(trackIds[i])) {
            outItems.append(m_itemMap->value(trackIds[i]));
            ++ collected;
        }
    }
    return collected;
}

bool Collector::isValid() {
    return m_valid;
}

void Collector::setValid(bool value) {
    m_valid = value;
}




void Collector::populateAuxStrcts() {
    for(QHash<QString, CollectorItem *>::iterator it = m_itemMap->begin();
        it != m_itemMap->end();
        ++it) {
        addToAuxStructs(it.value()) ;
    }
}


void Collector::addToAuxStructs(QList<CollectorItem *>* tracks) {
    foreach(CollectorItem *track, (*tracks)) {
        addToAuxStructs(track);
    }
}

void Collector::removeFromAuxStructs(QList<QString>* trackIds) {
    QList<CollectorItem *> toBeRemoved;
    items(*trackIds, toBeRemoved);

    ArtistInfo *fromAllArtists = m_artistInfo.value(ALL_ARTISTS);

    foreach(CollectorItem *item, toBeRemoved) {
        ArtistInfo *artist = m_artistInfo[item->artist()];
        AlbumInfo  *album  = artist->m_albums->value(item->album());
        AlbumInfo  *fromAllAlbums = artist->m_albums->value(ALL_ALBUMS);
        album->m_tracks->removeOne(item);
        fromAllAlbums->m_tracks->removeOne(item);
        if(album->m_tracks->isEmpty()) {
            artist->m_albums->remove(album->m_albumName);
            fromAllArtists->m_albums->remove(artist->m_artistName+":"+album->m_albumName);
            if(artist->m_albums->size() == 1) { //i.e has only <_ALL_ALBUMS_> entry left
                artist->m_albums->remove(ALL_ALBUMS);
                m_artistInfo.remove(artist->m_artistName);
                delete artist; //ArtistInfo's destructor will take care of deleting album.
            }
            else {
                delete album;
            }
        }
    }
}

void Collector::addToAuxStructs(CollectorItem *track) {
    ArtistInfo *artist;
    if(! m_artistInfo.contains(track->artist())) {
        artist = new ArtistInfo(track->artist());
        artist->m_albums->insert(ALL_ALBUMS, new AlbumInfo(ALL_ALBUMS, artist));
        m_artistInfo.insert(track->artist(), artist);
    }
    else {
        artist = m_artistInfo.value(track->artist());
    }
    AlbumInfo *album;
    //This is a redundant check in case of a new artist. But keeps the code clean.
    if(! artist->m_albums->contains(track->album())) {
        album = new AlbumInfo(track->album(), artist);
        album->m_numPlays  = track->playCount();
        album->m_avgRating = (int) track->rating();
        album->m_tracks->append(track);
        artist->m_albums->insert(album->m_albumName, album);
    }
    else {
        album = artist->m_albums->value(track->album());
        album->m_tracks->append(track);
        album->m_numPlays += track->playCount();
        if(track->rating() != None) {
            album->m_avgRating = (album->m_avgRating + (int) track->rating()) / 2;
        }
    }
    artist->m_albums->value(ALL_ALBUMS)->m_tracks->append(track);
    m_artistInfo.value(ALL_ARTISTS)->m_albums->insert(
                artist->m_artistName+":"+album->m_albumName, album);
}

void Collector::clearAuxStructs() {
    for(QHash<QString, ArtistInfo *>::iterator it = m_artistInfo.begin();
        it != m_artistInfo.end();
        ++ it) {
        delete it.value(); //Destructor of ArtistInfo will do the remaining job.
    }
    m_artistInfo.clear();
}


int Collector::itemsFromArtist(QString artistName, QList<CollectorItem *> &outItems) {
    int collected = 0;
    if(m_artistInfo.contains(artistName)) {
        ArtistInfo *artist = m_artistInfo.value(artistName);
//        for(QHash<QString, AlbumInfo *>::iterator it = artist->m_albums->begin();
//            it != artist->m_albums->end();
//            ++ it) {
//            AlbumInfo *album = it.value();
//            outItems.append((*album->m_tracks));
//            collected += album->m_tracks->size();
//        }
        if(artist->m_artistName != ALL_ARTISTS) {
            AlbumInfo *all = artist->m_albums->value(ALL_ALBUMS);
            collected = all->m_tracks->size();
            outItems.append(*all->m_tracks);
        }
    }
    return collected;
}



int Collector::itemsFromAlbum(QString albumName, QString artistName, QList<CollectorItem *> &outItems) {
    QHash<QString, AlbumInfo *> *albums = m_artistInfo.value(ALL_ARTISTS)->m_albums;
    QString artistAlbum = artistName+":"+albumName;
    if(albums->contains(artistAlbum)) {
        outItems.append( *albums->value(artistAlbum)->m_tracks);
        return albums->value(artistAlbum)->m_tracks->size();
    }
    return 0;
}

int Collector::albumsFromArtist(QString artistName, QList<AlbumInfo *> &albums) {
    if(m_artistInfo.contains(artistName)) {
        ArtistInfo *artist = m_artistInfo[artistName];
        albums.append(artist->m_albums->values());
        return artist->m_albums->values().size();
    }
    return 0;
}


ArtistInfo *Collector::artist(QString artistName) {
    if(m_artistInfo.contains(artistName)) {
        return m_artistInfo[artistName];
    }
    return  0;
}


QList<ArtistInfo *> Collector::allArtists() {
    return m_artistInfo.values();
}
